package main

import (
	"context"
	"errors"
	"fmt"
	"gluenet/internal/agent/api"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/wutianze/nats.go"
	"golang.org/x/sys/unix"
)

const (
	envPerfix = `transfer`

	keyAddr = `addr`
	keyNats = `nats`

	keyPushTarget = `push.target`
)

func main() {
	if err := cmd().Execute(); err != nil && err != http.ErrServerClosed {
		log.Fatal("transfer failed: ", err)
	}
}

func cmd() *cobra.Command {
	vp := newViper()
	initLog(vp)
	rootCmd := &cobra.Command{
		Use:   "gluenet transfer",
		Short: "",
		Long:  "",
		RunE: func(cmd *cobra.Command, args []string) error {
			return runTransfer(vp)
		},
	}

	flags := rootCmd.Flags()
	flags.String(
		keyAddr,
		":10099",
		"Address on which to listen",
	)
	flags.String(
		keyNats,
		"",
		"Nats url on which to connect Nats",
	)
	flags.String(
		keyPushTarget,
		"",
		"Targets to push",
	)

	vp.BindPFlags(flags)

	return rootCmd
}

func runTransfer(vp *viper.Viper) error {
	addr := vp.GetString(keyAddr)
	natsUrl := vp.GetString(keyNats)
	targets := vp.GetString(keyPushTarget)

	transfer, err := NewGluenetTransfer(natsUrl, targets)
	if err != nil {
		return fmt.Errorf("init transfer err: %s", err)
	}

	rt := http.NewServeMux()
	transfer.InitRouter(rt)

	server := &http.Server{
		ReadTimeout:  60 * time.Second,
		WriteTimeout: 60 * time.Second,
		Handler:      rt,
		Addr:         addr,
	}

	go func() {
		sigs := make(chan os.Signal, 1)
		signal.Notify(sigs, unix.SIGINT, unix.SIGTERM)
		<-sigs

		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		if err := server.Shutdown(ctx); err != nil {
			logrus.Error(err)
		}

		cancel()
	}()

	logrus.Info(
		"transfer listen at: ", addr,
		" | api: ", api.WriteMetric,
		" | using nats: ", natsUrl,
		" | targets: ", targets,
		" | log level: ", vp.GetString("log"),
	)

	return server.ListenAndServe()
}

func newViper() *viper.Viper {
	vp := viper.New()
	vp.SetEnvPrefix(envPerfix)
	vp.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	vp.AutomaticEnv()

	return vp
}

func initLog(vp *viper.Viper) {
	switch vp.GetString("log") {
	case "ERROR":
		logrus.SetLevel(logrus.ErrorLevel)
	case "WARN":
		logrus.SetLevel(logrus.WarnLevel)
	case "DEBUG":
		logrus.SetLevel(logrus.DebugLevel)
	case "TRACE":
		logrus.SetLevel(logrus.TraceLevel)
	default:
		logrus.SetLevel(logrus.InfoLevel)
	}
}

var nc *nats.Conn

type GluenetTransfer struct {
	Targets string
}

func NewGluenetTransfer(natUrl, targets string) (*GluenetTransfer, error) {
	var err error

	nc, err = nats.IConnect(natUrl)
	if err != nil {
		return nil, err
	}

	if targets == "" {
		return nil, errors.New("no target setted")
	}

	return &GluenetTransfer{
		Targets: targets,
	}, nil

}

func (s *GluenetTransfer) InitRouter(rt *http.ServeMux) {
	rt.HandleFunc(api.WriteMetric, s.transfer)
}

func (s *GluenetTransfer) transfer(w http.ResponseWriter, r *http.Request) {
	data, err := io.ReadAll(r.Body)
	if err != nil {
		logrus.Error("read data falied: ", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if err := nc.Publish(s.Targets, data); err != nil {
		logrus.Error("transfer data falied: ", err)
	}

	w.WriteHeader(http.StatusNoContent)
	logrus.Debug("tranfer data to: ", s.Targets)
}
